/*
 * paging.c - Mokri
 * Contributors: xMine
 * (Any new Contributor may add himself/herself to the list)
 * Changes and fixes to be made:
 * Source:
 */

#include "paging.h"

uint32_t *page_directory;

/* Mappt den kompletten Speicher (identy mapping) und aktiviert danach Paging
 * Parameter: Größe des RAMs von GRUB in KiloBytes!
 * Rückgaben: Keine
 */
void paging_init (uint32_t ramsize) {
	uint32_t *page_table;
	uint32_t ram = ramsize/1024; // Ramgröße in MB umrechnen

	page_directory = (uint32_t*)pmm_alloc (); // 4kb Speicher anfordern
	if (page_directory == NULL) {
		kernel_panic ("paging_init failed: Could not allocate space for Page Directory!");
	}

	int i;
	for (i=0; i<1024; i++) { // Alle Pages auf "nicht present" setzen
		page_directory[i] = 0|2; // Attribute: Supervisor Level, Schreiben/Lesen, nicht present
	}

	uint32_t j, k, address = 0x0;
	for (k=0; k<ram; k++) { // Diese Schleife mappt jeden MB des Rams in das Page Directory
		page_table = (uint32_t*)pmm_alloc (); // Speicher für die PageTable holen
		if (page_table == NULL) {
			kernel_panic ("paging_init failed: Could not allocate space for Page Table!");
		}

		for (j=0; j<1024; j++) {
			page_table[j] = address | 0x03; // Attribute: Supervisor Level, Schreiben/Lesen, nicht present
			address += 0x1000;
		}
		
		page_directory[k] = (uint32_t)page_table;
		page_directory[k] |= 0x03; // Attribute: SU Level, Schreiben/Lesen, Present
	}

	// Paging Directory Adresse übergeben
	asm volatile ("mov %0, %%cr3" :: "b"(page_directory));

	// Paging AKTIVIEREN
	unsigned int cr0;
	asm volatile ("mov %%cr0, %0" : "=b"(cr0));
	cr0 |= 0x80000000;
	asm volatile ("mov %0, %%cr0" :: "b"(cr0));

}

/* Nimmt eine virtuelle Adresse entgegen und gibt ihre echte physische Position zurück
 * Parameter: Virtuelleadresse
 * Rückgaben: Physischeadresse
 */
void *paging_get_physical_address (void *vaddr) {
	uint32_t pdir = (uint32_t)vaddr >> 22; // Index in dem Page Directory
	uint32_t ptab = (uint32_t)vaddr >> 12 & 0x03FF; // Index in der Page Table
	
	uint32_t *table = (uint32_t*)(page_directory[pdir] & ~(0xFFF));
	
	return (void*)( (table[ptab] & ~0xFFF) + ((uint32_t)vaddr & 0xFFF) );
}

/* Mappt eine physische Page in den virtuellen Adressraum
 * Parameter: Adresse der Page, Neue virtuelle Adresse der Page
 * Rückgaben: Keine
 */
void paging_map_page (void *paddr, void *vaddr, uint16_t flags) {
	uint32_t pdir = (uint32_t)vaddr >> 22; // Index in dem Page Directory
	uint32_t ptab = (uint32_t)vaddr >> 12 & 0x03FF; // Index in der Page Table
	
	uint32_t *table = (uint32_t*)(page_directory[pdir] & ~(0xFFF));
	
	table[ptab] = ((uint32_t)paddr) | (flags & 0xFFF); // Page gleich auf präsent setzen
	
	paging_flush_cache (vaddr);
}

/* Mappt einen Bereich von physikalischen Speicher in den virtuellen Adressraum
 * Parameter: Startadresse (phys.), Länge, Startadresse (virt.), flags
 * Rückgaben: Keine
 */
void paging_map_range (void *paddr, uint32_t size, void *vaddr, uint16_t flags) {
	
	if (size % 4096 > 0) { // Ggf. selber alignen
		size += 4096 - size%4096;
	}
	
	void *endaddr = (void*)((uint32_t)paddr + size);
	
	while (paddr < endaddr) {
		paging_map_page (paddr, vaddr, flags);
		paddr += 0x1000;
		vaddr += 0x1000;
	}
}

/* Setzt eine Page auf nicht present
 * Parameter: Adresse im virtuellen Raum
 * Rückgaben: Keine
 */
void paging_set_page_not_present (void *vaddr) {
	uint32_t pdir = (uint32_t)vaddr >> 22; // Index in dem Page Directory
	uint32_t ptab = (uint32_t)vaddr >> 12 & 0x03FF; // Index in der Page Table
	
	uint32_t *table = (uint32_t*)(page_directory[pdir] & ~(0xFFF));
	
	table[ptab] &= ~PAGE_FLAG_PRESENT; // Present Bit löschen
	
	paging_flush_cache (vaddr);
}

/* Überprüft ob eine Page Present ist
 * Parameter: Adresse im virtuellen Raum
 * Rückgaben: TRUE wenn präsent, ansonsten FALSE
 */
BOOL paging_is_page_present (void *vaddr) {
	uint32_t pdir = (uint32_t)vaddr >> 22; // Index in dem Page Directory
	uint32_t ptab = (uint32_t)vaddr >> 12 & 0x03FF; // Index in der Page Table
	
	uint32_t *table = (uint32_t*)(page_directory[pdir] & ~(0xFFF));
	
	return (table[ptab] & PAGE_FLAG_PRESENT);
}

/* Veranlasst die Leerung des Cache in Falle einer Änderung in einer der Tables
 * Parameter: Keine
 * Rückgaben: Keine
 */
void inline paging_flush_cache (void *vaddr) {
	#if 1
	// Besser, da es nur eine Seite invalidiert und nicht alle
	asm volatile("invlpg %0"::"m" (*(char *)vaddr));
	#else
	// Das hier ist wieder Abwärtskompatibel, aber dafür langsamer, da alle Pages neu geladen werden müssen
	asm volatile (  "mov %%cr3, %%eax\n"
			"mov %%eax, %%cr3\n" ::: "%eax");
	#endif
}

void paging_page_fault (struct stack_frame *sf) {
	kernel_panic ("TODO: paging_page_fault implementieren");
}
